#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "value.h"
#include "z_memory.h"

static float NaN = 0.0f / 0.0f;

value_t *value_new() {
    value_t *value = z_calloc(1, sizeof(value_t));
    value_set_null(value);
    return value;
}

void value_free(value_t *value) {
    z_free(value);
}

void value_set_null(value_t *value) {
    bzero(value, sizeof(value_t));
    value->value = NaN;
    value->quality = good;
}

void value_set_bit(value_t *value, _Bool val) {
    value->clazz = vc_bit;
    value->size = 1;
    value->bval = val;
}

void value_set_short(value_t *value, int16_t val) {
    value->clazz = vc_short;
    value->size = sizeof(int16_t);
    value->ival = val;
}

void value_set_int(value_t *value, int32_t val) {
    value->clazz = vc_int;
    value->size = sizeof(int32_t);
    value->ival = val;
}

void value_set_long(value_t *value, int64_t val) {
    value->clazz = vc_long;
    value->size = sizeof(int64_t);
    value->ival = val;
}

void value_set_float(value_t *value, float val) {
    value->clazz = vc_float;
    value->size = sizeof(float);
    value->fval = val;
}

void value_set_double(value_t *value, double val) {
    value->clazz = vc_double;
    value->size = sizeof(double);
    value->dval = val;
}

void value_copy(value_t *dst, const value_t *src) {
    if (!dst || !src)
        return;

    dst->clazz = src->clazz;
    dst->_val = src->_val;
    dst->value = src->value;
    dst->timestamp = src->timestamp;
    dst->quality = src->quality;
}

void value_printf(char *buf, const value_t *value) {
    char *ptr = buf;
    ptr += sprintf(ptr, "c=%c,ts=%ld,", value->clazz, value->timestamp);
    switch (value->clazz) {
        case vc_bit:
            sprintf(ptr, "v=%d", value->bval ? 1 : 0);
            break;
        case vc_short:
        case vc_int:
        case vc_long:
            sprintf(ptr, "v=%ld,val=%f", value->ival, value->value);
            break;
        case vc_float:
            sprintf(ptr, "v=%f", value->fval);
            break;
        case vc_double:
            sprintf(ptr, "v=%lf", value->dval);
            break;
        default:
            sprintf(ptr, "v=%d", 0);
            break;
    }
}

void value_scanf(char *buf, value_t *value) {
    if (!buf || !value) { return; }

    value_set_null(value);

    char *ptr, *save = NULL;
    ptr = strtok_r(buf, ",", &save);
    do {
        if (0 == strncmp("c=", ptr, 2)) {
            value->clazz = ptr[2];
        } else if (0 == strncmp("ts=", ptr, 3)) {
            value->timestamp = atol(ptr + 3);
        } else if (0 == strncmp("v=", ptr, 2)) {
            ptr = ptr + 2;
            switch (value->clazz) {
                case vc_bit:
                case vc_short:
                case vc_int:
                case vc_long:
                    value_set_long(value, atoll(ptr));
                    value->value = value->ival;
                    break;
                case vc_float:
                    value_set_float(value, (float) atof(ptr));
                    value->value = value->fval;
                    break;
                case vc_double:
                    value_set_double(value, atof(ptr));
                    value->value = value->dval;
                    break;
                default:
                    value_set_null(value);
                    break;
            }
        } else if (0 == strncmp("val=", ptr, 4)) {
            ptr = ptr + 4;
            value->value = (float) atof(ptr);
        }
    } while (NULL != (ptr = strtok_r(NULL, ",", &save)));
}

static const char *hex = {"0123456789abcdef"};

static int chr2hex(char c) {
    if (c >= '0' && c <= '9')
        return c - '0';
    else if (c >= 'a' && c <= 'f')
        return c - 'a' + 10;
    else if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;
    else
        return -1;
}

char *bin2str(const uint8_t *bin, size_t nbin, char *str, size_t *nstr) {
    int i;

    if (NULL == nstr)
        return NULL;

    if (*nstr < nbin * 2 + 1) {
        *nstr = nbin * 2 + 1;
        return NULL;
    }

    for (i = 0; i < nbin; ++i) {
        str[i * 2] = hex[(bin[i] >> 4) & 0x0f];
        str[i * 2 + 1] = hex[bin[i] & 0x0f];
    }
    *nstr = i * 2;
    str[*nstr] = '\0';

    return str;
}

uint8_t *str2bin(const char *str, uint8_t *bin, size_t *nbin) {
    int i, j;
    int len = strlen(str);

    if (NULL == nbin) {
        return NULL;
    }

    if (*nbin < len / 2) {
        *nbin = len / 2;
        return NULL;
    }

    for (i = 0, j = 0; j < len; ++i, j += 2) {
        bin[i] = chr2hex(str[j]) * 0x10 + chr2hex(str[j + 1]);
    }
    *nbin = i;

    return bin;
}
